package com.management.department.teacherinfosystem.Service;

import com.management.department.teacherinfosystem.DTO.StudentGuidanceStatsDTO;
import com.management.department.teacherinfosystem.DTO.StudentGuidanceSubmitDTO;
import com.management.department.teacherinfosystem.Entity.StudentGuidance;
import com.management.department.teacherinfosystem.Entity.StudentGuidanceAchievement;
import com.management.department.teacherinfosystem.Entity.User;
import com.management.department.teacherinfosystem.Repository.StudentGuidanceAchievementRepository;
import com.management.department.teacherinfosystem.Repository.StudentGuidanceRepository;
import com.management.department.teacherinfosystem.Repository.UserRepository;
import jakarta.persistence.criteria.Join;
import jakarta.persistence.criteria.JoinType;
import jakarta.persistence.criteria.Predicate;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class StudentGuidanceServiceImpl implements StudentGuidanceService {

    private final StudentGuidanceRepository guidanceRepository;
    private final StudentGuidanceAchievementRepository achievementRepository;
    private final UserRepository userRepository;

    @Override
    @Transactional(readOnly = true)
    public Page<StudentGuidance> listMy(Long userId, Pageable pageable, String studentType, String achievementType, String level, String year, String status) {
        Specification<StudentGuidance> spec = (root, query, cb) -> {
            // 预先抓取教师与成果，避免 Lazy 初始化导致的 JSON 序列化问题
            root.fetch("user", JoinType.LEFT);
            root.fetch("achievements", JoinType.LEFT);
            query.distinct(true);
            List<Predicate> ps = new ArrayList<>();
            ps.add(cb.equal(root.get("user").get("id"), userId));
            if (studentType != null && !studentType.isBlank()) {
                ps.add(cb.equal(root.get("studentType"), studentType));
            }
            if (status != null && !status.isBlank()) {
                ps.add(cb.equal(root.get("status"), normalizeStatus(status)));
            }
            // achievement filters via join
            boolean needJoin = (achievementType != null && !achievementType.isBlank()) ||
                    (level != null && !level.isBlank()) ||
                    (year != null && !year.isBlank());
            if (needJoin) {
                Join<StudentGuidance, StudentGuidanceAchievement> ach = root.join("achievements", JoinType.LEFT);
                if (achievementType != null && !achievementType.isBlank()) {
                    ps.add(cb.equal(ach.get("achievementType"), achievementType));
                }
                if (level != null && !level.isBlank()) {
                    ps.add(cb.equal(ach.get("level"), level));
                }
                if (year != null && !year.isBlank()) {
                    ps.add(cb.equal(cb.function("YEAR", Integer.class, ach.get("achievementDate")), Integer.valueOf(year)));
                }
                query.distinct(true);
            }
            return cb.and(ps.toArray(new Predicate[0]));
        };
        return guidanceRepository.findAll(spec, pageable);
    }

    @Override
    @Transactional(readOnly = true)
    public Page<StudentGuidance> listAll(Pageable pageable, Long teacherId, String teacherName, String studentType, String achievementType, String level, String year, String status) {
        Specification<StudentGuidance> spec = (root, query, cb) -> {
            // 预先抓取教师与成果，避免 Lazy 初始化导致的 JSON 序列化问题
            root.fetch("user", JoinType.LEFT);
            root.fetch("achievements", JoinType.LEFT);
            query.distinct(true);
            List<Predicate> ps = new ArrayList<>();
            if (teacherId != null) {
                ps.add(cb.equal(root.get("user").get("id"), teacherId));
            }
            if (teacherName != null && !teacherName.isBlank()) {
                ps.add(cb.like(cb.lower(root.get("user").get("name")), "%" + teacherName.toLowerCase() + "%"));
            }
            if (studentType != null && !studentType.isBlank()) {
                ps.add(cb.equal(root.get("studentType"), studentType));
            }
            if (status != null && !status.isBlank()) {
                ps.add(cb.equal(root.get("status"), normalizeStatus(status)));
            }
            boolean needJoin = (achievementType != null && !achievementType.isBlank()) ||
                    (level != null && !level.isBlank()) ||
                    (year != null && !year.isBlank());
            if (needJoin) {
                Join<StudentGuidance, StudentGuidanceAchievement> ach = root.join("achievements", JoinType.LEFT);
                if (achievementType != null && !achievementType.isBlank()) {
                    ps.add(cb.equal(ach.get("achievementType"), achievementType));
                }
                if (level != null && !level.isBlank()) {
                    ps.add(cb.equal(ach.get("level"), level));
                }
                if (year != null && !year.isBlank()) {
                    ps.add(cb.equal(cb.function("YEAR", Integer.class, ach.get("achievementDate")), Integer.valueOf(year)));
                }
                query.distinct(true);
            }
            return cb.and(ps.toArray(new Predicate[0]));
        };
        return guidanceRepository.findAll(spec, pageable);
    }

    @Override
    @Transactional(readOnly = true)
    public StudentGuidance getById(Long id) {
        StudentGuidance g = guidanceRepository.findWithUserAndAchievementsById(id).orElse(null);
        return g;
    }

    @Override
    @Transactional
    public Long create(Long userId, StudentGuidanceSubmitDTO dto) {
        User user = userRepository.findById(userId).orElseThrow(() -> new RuntimeException("用户不存在"));
        StudentGuidance g = mapDtoToEntity(new StudentGuidance(), dto);
        g.setUser(user);
        g.setStatus("PENDING");
        StudentGuidance saved = guidanceRepository.save(g);
        if (dto.getAchievements() != null) {
            for (StudentGuidanceSubmitDTO.AchievementDTO a : dto.getAchievements()) {
                StudentGuidanceAchievement ach = mapAchievementDto(a);
                ach.setGuidance(saved);
                achievementRepository.save(ensureExtraJson(ach));
            }
        }
        return saved.getId();
    }

    @Override
    @Transactional
    public void update(Long id, Long userId, StudentGuidanceSubmitDTO dto) {
        StudentGuidance g = guidanceRepository.findById(id).orElseThrow(() -> new RuntimeException("记录不存在"));
        if (!Objects.equals(g.getUser().getId(), userId)) {
            throw new RuntimeException("无权修改该记录");
        }
        mapDtoToEntity(g, dto);
        // 教职工更新后回到待审核状态，清空审核信息
        g.setStatus("PENDING");
        g.setReviewer(null);
        g.setReviewOpinion(null);
        g.setReviewTime(null);
        guidanceRepository.save(g);
        // 简化：重建成果（避免触发 LAZY 集合，直接按 guidanceId 删除重建）
        achievementRepository.deleteByGuidance_Id(g.getId());
        if (dto.getAchievements() != null) {
            for (StudentGuidanceSubmitDTO.AchievementDTO a : dto.getAchievements()) {
                StudentGuidanceAchievement ach = mapAchievementDto(a);
                ach.setGuidance(g);
                achievementRepository.save(ensureExtraJson(ach));
            }
        }
    }

    @Override
    @Transactional
    public void delete(Long id, Long userId) {
        StudentGuidance g = guidanceRepository.findById(id).orElseThrow(() -> new RuntimeException("记录不存在"));
        if (!Objects.equals(g.getUser().getId(), userId)) {
            throw new RuntimeException("无权删除该记录");
        }
        guidanceRepository.delete(g);
    }

    @Override
    @Transactional
    public void review(Long id, Long reviewerId, String status, String opinion) {
        StudentGuidance g = guidanceRepository.findById(id).orElseThrow(() -> new RuntimeException("记录不存在"));
        g.setStatus(normalizeStatus(status));
        User reviewer = new User();
        reviewer.setId(reviewerId);
        g.setReviewer(reviewer);
        g.setReviewOpinion(opinion);
        g.setReviewTime(LocalDateTime.now());
        guidanceRepository.save(g);
    }

    @Override
    @Transactional(readOnly = true)
    public StudentGuidanceStatsDTO stats(Long teacherId, String studentType, String achievementType, String level, String year) {
        // 基于条件统计
        Specification<StudentGuidance> baseSpec = (root, query, cb) -> {
            List<Predicate> ps = new ArrayList<>();
            if (teacherId != null) ps.add(cb.equal(root.get("user").get("id"), teacherId));
            if (studentType != null && !studentType.isBlank()) ps.add(cb.equal(root.get("studentType"), studentType));
            return cb.and(ps.toArray(new Predicate[0]));
        };
        long totalGuidance = guidanceRepository.count(baseSpec);

        // 取出满足 baseSpec 的所有记录的成就进行分类统计
        Specification<StudentGuidance> achSpec = (root, query, cb) -> {
            List<Predicate> ps = new ArrayList<>();
            if (teacherId != null) ps.add(cb.equal(root.get("user").get("id"), teacherId));
            if (studentType != null && !studentType.isBlank()) ps.add(cb.equal(root.get("studentType"), studentType));
            Join<StudentGuidance, StudentGuidanceAchievement> ach = root.join("achievements", JoinType.LEFT);
            if (achievementType != null && !achievementType.isBlank()) ps.add(cb.equal(ach.get("achievementType"), achievementType));
            if (level != null && !level.isBlank()) ps.add(cb.equal(ach.get("level"), level));
            if (year != null && !year.isBlank()) ps.add(cb.equal(cb.function("YEAR", Integer.class, ach.get("achievementDate")), Integer.valueOf(year)));
            query.distinct(true);
            return cb.and(ps.toArray(new Predicate[0]));
        };
        List<StudentGuidance> list = guidanceRepository.findAll(achSpec);
        // explicitly touch achievements to initialize within transaction
        list.forEach(g -> g.getAchievements().size());
        long totalAchievements = list.stream().mapToLong(g -> (long) g.getAchievements().size()).sum();
        long award = list.stream().flatMap(g -> g.getAchievements().stream()).filter(a -> "获奖".equals(a.getAchievementType())).count();
        long paper = list.stream().flatMap(g -> g.getAchievements().stream()).filter(a -> "论文".equals(a.getAchievementType())).count();
        long project = list.stream().flatMap(g -> g.getAchievements().stream()).filter(a -> "项目".equals(a.getAchievementType())).count();
        long excellent = list.stream().flatMap(g -> g.getAchievements().stream()).filter(a -> "优秀毕业论文".equals(a.getAchievementType())).count();
        return new StudentGuidanceStatsDTO(totalGuidance, totalAchievements, award, paper, project, excellent);
    }

    private StudentGuidance mapDtoToEntity(StudentGuidance g, StudentGuidanceSubmitDTO dto) {
        g.setStudentType(dto.getStudentType());
        g.setStudentName(dto.getStudentName());
        g.setGuidanceCategory(dto.getGuidanceCategory());
        g.setGuidanceTopic(dto.getGuidanceTopic());
        g.setStartDate(LocalDate.parse(dto.getStartDate()));
        g.setEndDate(LocalDate.parse(dto.getEndDate()));
        return g;
    }

    private StudentGuidanceAchievement mapAchievementDto(StudentGuidanceSubmitDTO.AchievementDTO a) {
        StudentGuidanceAchievement ach = new StudentGuidanceAchievement();
        ach.setAchievementType(a.getAchievementType());
        ach.setTitle(a.getTitle());
        ach.setLevel(a.getLevel());
        if (a.getAchievementDate() != null && !a.getAchievementDate().isBlank()) {
            ach.setAchievementDate(LocalDate.parse(a.getAchievementDate()));
        }
        ach.setExtra(a.getExtra());
        return ach;
    }

    private String normalizeStatus(String s) {
        if (s == null || s.isBlank()) return "PENDING";
        if ("APPROVED".equalsIgnoreCase(s) || "已通过".equals(s)) return "APPROVED";
        if ("REJECTED".equalsIgnoreCase(s) || "已驳回".equals(s)) return "REJECTED";
        if ("PENDING".equalsIgnoreCase(s) || "审核中".equals(s)) return "PENDING";
        return s;
    }

    private StudentGuidanceAchievement ensureExtraJson(StudentGuidanceAchievement ach) {
        try {
            String extra = ach.getExtra();
            if (extra == null || extra.trim().isEmpty()) {
                ach.setExtra("{}");
                return ach;
            }
            String trimmed = extra.trim();
            // 简单校验：如果不是以 { 或 [ 开头，包一层对象
            if (!(trimmed.startsWith("{") || trimmed.startsWith("["))) {
                ach.setExtra(new ObjectMapper().writeValueAsString(java.util.Map.of("note", extra)));
                return ach;
            }
            // 尝试解析确保是合法 JSON
            new ObjectMapper().readTree(trimmed);
            ach.setExtra(trimmed);
            return ach;
        } catch (Exception e) {
            // 兜底：写入空对象，避免数据库 JSON 校验失败
            ach.setExtra("{}");
            return ach;
        }
    }
} 